<template>
  <div class="clearfix gui-padding">
    <a-upload :file-list="fileList" @change="handleChange" @remove="handleRemove">
      <a-button>
        <upload-outlined></upload-outlined>
        选择一个文件
      </a-button>
    </a-upload>
    <a-progress type="circle" :percent="successChunkPercent" />
    <div>
      <ul>
        <li> 1: 当前文件是：{{ currentFile }}</li>
        <li> 2: 文件的后缀：{{ fileSuffix }}</li>
        <li> 3: 当前文件MD5值是：{{ fileMd5 }}</li>
        <li> 4: 文件的大小：{{ currentFile.size }}, 分片数量：{{ chunkCount }}，每片大小：{{ chunkSize }}</li>
        <li> 5: 文件分片信息：{{ chunkList }}</li>
        <li> 6: 上传成功分片的数量：{{ chunkUploaed }}</li>
        <li> 7: 上传成功的文件外链：{{ result }}</li>
      </ul>
    </div>
  </div>
</template>
<script setup>
import md5 from 'js-md5'
import {getBaseURL} from "~/config/common.js";
// 1: 选择一个文件
// 2: 把文件生成一个md5，确定文件唯一性，也是它的唯一标识 `yarn add js-md5`
// 3: 进行文件分割，确定分割文件的大小，把分割的文件放入数组中
// 4: 调用promise.all把这个分割的文件选择调用上传方法，其实就是所谓并发上传，这个promise.all.可以监听所以的异步请求是否成功，都成功才会成功，只要有一个失败就失败。
// 5: 定义计数器，在promise.all去累加，如果上传成功一次就+1.如果次数已经等于了分片数量。就调用合并请求方法。把上传成功的分片就行合并，
// 6: 返回合并以后的文件外链。结束
const fileList = ref([]);
const uploading = ref(false);
const currentFile = ref({})// 获取当前的上传文件
const fileMd5 = ref("")// 文件的md5值
const chunkCount = ref(0) // 分片的数量
const chunkSize = ref(1024 * 1024 * 32) // 分片的大小,每片是32M
const chunkUploaed = ref(0) // 这个值就是去监听每个分片上传成功的计数器。 上传成功一个分片就+1. 如果这个值==chunkCount就触发调用合并接口进行文件的合并，返回合并文件信息
const fileSuffix = ref("") // 文件后缀，给合并和判断是否存在使用，因为分片文件是没有后缀，所以在合并的时候，一定要把后缀带到服务器才知道生合并以后生成的具体文件是什么。
const chunkList = ref([]) // 装在每个分片的信息，主要就是记录上传文件的大小。
const result = ref("")// 接收上传成功的文件外链
const successChunkPercent = ref(0)
// 文件选择触发的方法
const handleChange = (info) => {
  //1: 获取上传的文件对象
  const file = info.file;
  // 2: 开始去读取文件的内容，大家思考为什么要去读取文件的内容进行md5而不是拿文件名md5.
  const reader = new FileReader();
  reader.readAsArrayBuffer(file.originFileObj)
  reader.onload = (event) => {
    // 3: 获取文件的md5值
    const fileContent = event.target.result
    fileMd5.value = md5(fileContent).toString();
    currentFile.value = file.originFileObj
    // 4: 开始计算分片的数量
    chunkCount.value = Math.ceil(file.size / chunkSize.value)
    // 5: 这里每次都要重新初始化已经上传的数量, 这个值就是去监听每个分片上传成功的计数器。
    chunkUploaed.value = 0
    successChunkPercent.value = 0;
    result.value = ""
    // 6: 获取文件后缀
    fileSuffix.value = "." + file.name.split(".").pop()
    // 7：分片大小的存储
    for (let i = 0; i < chunkCount.value; i++) {
      const start = i * chunkSize.value;
      const end = Math.min(start + chunkSize.value - 1, currentFile.value.size - 1);
      chunkList.value[i] = currentFile.value.slice(start, end)
    }
    // 8: 执行上传
    handleUpload()
  }

  reader.onerror = error=>{
    console.log("读取文件发生错误",error)
    successChunkPercent.value = 0;
    useMessage().error("读取文件发生错误!")
  }
}


// 文件上传的方法 axios+elementplus
// 文件上传的方法 axios+antdesgin vue
const handleUpload = async () => {
  // 1:秒杀 axios
  const resp = await useHttpGet(`/minio/checkExits?md5=${fileMd5.value}&fileSuffix=${fileSuffix.value}`)
  if(resp.data) {
     useMessage().success("文件上传成功!");
     successChunkPercent.value = 100
     result.value = resp.data
     return;
  }
  // 2: 开始上传分片
  const chuckPromise = []
  for (let i = 0; i < chunkList.value.length; i++) {
    chuckPromise.push(uploadChunk(chunkList.value[i],i + 1,()=>{
      successChunkPercent.value  +=  99 / chunkCount.value;
      chunkUploaed.value++;
    }))
  }
  // 这里就是核心，就把所以的分片上传，放入promise.all中。刚好uploadChunk全部都是promise返回
  await Promise.all(chuckPromise)

  // 如果上传成功的分片和实际分派你的数量相等，就说明全面上传成功了。就可以执行合并
  if(chunkUploaed.value == chunkCount.value){
      const mergeResult = await useHttpPost(`/minio/merge?md5=${fileMd5.value}&fileSuffix=${fileSuffix.value}&chunkTotal=${chunkCount.value}`)
      if (mergeResult.data.startsWith("[miss_chunk]")) {
        useMessage().error('文件缺失，请重新上传')
        return
      }
    successChunkPercent.value = 100;
    // 上传成功，获取合并以后的文件信息
    result.value = mergeResult.data
  }
};

const uploadChunk = async (chunk, chunkIndex, callback) => {
  let retryTime = 5;
  try {
    const formData = new FormData()
    formData.append('md5', fileMd5.value)
    formData.append('chunkIndex', chunkIndex);
    formData.append('chunk', chunk);
    const resp = await useHttpUpload( '/minio/uploadfile', {body: formData})
    callback && callback(resp)
  }catch (e){
    if (retryTime > 0) {
      retryTime--;
      return uploadChunk(chunk, chunkIndex, callback);
    }
  }
}
const handleRemove = file => {

};

</script>

